Skip to content

perf(pos-app): speed up amount entry on low-end devices - #640

Draft
ignaciosantise wants to merge 2 commits into
mainfrom
feat/pos-numeric-text-input
Draft

ignaciosantise wants to merge 2 commits into
mainfrom
feat/pos-numeric-text-input

Conversation

@ignaciosantise

@ignaciosantise ignaciosantise commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Makes the amount screen respond faster on low-end Android POS hardware. The existing BigAmountInput animation and visuals are unchanged.

  • Stable keypad handler: app/amount.tsx now uses plain useState with a stable onKeyPress. Before, a new inline handler on every render stopped the memoized NumericKeyboard from skipping renders, so all 12 keys re-rendered on each press. This removes react-hook-form, which only this screen used; the lockfile change is just its own entries.
  • Cached separators: formatAmount.ts looks up the decimal and group separators once per locale. Before, each key press built three Intl.NumberFormat instances, which is slow on Android Hermes because it goes through Java ICU.
  • Reanimated sync UI props (Android only): ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS is turned on in package.json. A Perfetto profile showed about 37% of the UI thread going to Reanimated committing the full shadow tree on every animation frame. With the flag, transform and opacity updates skip that commit. The iOS equivalent is deliberately left off: that cost was only measured on low-end Android, and the flag would change every Reanimated animation on iOS.
  • AnimatedCharacter: plain Text instead of Animated.Text.

Measurements

Release builds on a low-end Android POS terminal (MediaTek MT6761, 4× Cortex-A53, 1.8 GB RAM). A scripted adb typing sequence (1234567.89 then 10 backspaces) ran ×5 per run, 3 baseline runs vs 2 final runs.

Per key press (p50) Before After
JS work, normal typing (250ms between taps) 77ms 37ms
Key press → next frame, normal typing 103ms 60ms
JS work, rapid typing (~80ms between taps) 87ms 39ms
Key press → next frame, rapid typing 185ms 94ms

The janky-frame % under rapid typing did not change (~14%).

Testing

  • Android release build on the POS terminal: the amount renders the same as main, and every scripted tap registered
  • iOS simulator (dev JS on an existing binary): the amount renders correctly. An earlier version of this change used a zero-width translateX anchor, which broke iOS; it was reverted.
  • npm test (309 passing), lint, type check
  • Web
  • Android: other animated screens (scan/loading, payment success, button press feedback) look right with the Reanimated flag on

Native config changed on Android (Reanimated static flag), so Android builds need a clean prebuild. iOS native is unchanged.

🤖 Generated with Claude Code

- Keep the keypad's onKeyPress stable (plain useState + functional update) so
  the memoized NumericKeyboard no longer re-renders all 12 keys per press;
  drops react-hook-form, which only this screen used.
- Cache decimal/group separators per locale instead of constructing three
  Intl.NumberFormat instances per keypress (slow on Android Hermes).
- Enable Reanimated's synchronous UI-prop updates so transform/opacity
  animations skip the per-frame shadow-tree commit.
- Render amount characters with a plain Text.

Measured on a low-end Android POS terminal (MT6761): per-key JS work
77ms -> 37ms, keypress -> next frame 103ms -> 60ms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pos-demo Ready Ready Preview Oct 1, 2026 6:44pm UTC
2 Skipped Deployments
Project Deployment Actions Updated
react-native-appkit Ignored Ignored Preview Oct 1, 2026 6:44pm UTC
react-native-walletkit Ignored Ignored Preview Oct 1, 2026 6:44pm UTC

Request Review

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 2m 36s —— View job


Code Review

  • Read changed files
  • License compliance check
  • Breaking changes check
  • Post review findings

License compliance: ✅ No issues — react-hook-form (MIT) removed, no new deps introduced.

Breaking changes: ✅ No breaking changes — all exported symbols and component contracts are unchanged; pos-app is a private app.

Found 2 issue(s)

Issue 1: Unnecessary handlePress closure layer prevents full key memoization

ID: numeric-keyboard-handlepress-closure-a3f1
File: dapps/pos-app/components/numeric-keyboard.tsx:27-29
Severity: LOW
Category: performance

Context:

  • Pattern: handlePress is a local closure that just forwards to onKeyPress, then each Pressable wraps it in another arrow () => handlePress(key) — two closure layers with no benefit.
  • Risk: Every render of NumericKeyboardBase allocates 12 new closures (one per key). In normal usage the memoized NumericKeyboard doesn't re-render during typing, so this is benign — but if anything causes a keyboard re-render, the Pressable children all get new onPress references.
  • Impact: No functional bug, but the double-indirection obscures the intent and partially undercuts the memo optimization this PR adds.
  • Trigger: Any re-render of NumericKeyboardBase (theme change, orientation change).

Recommendation:

// Remove handlePress entirely; inline onKeyPress directly
onPress={() => onKeyPress(key)}

Fix this →


Issue 2: IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS is a native-config change with unchecked iOS test

ID: package-ios-sync-ui-props-untested-b8c2
File: dapps/pos-app/package.json:29-34
Severity: MEDIUM
Category: test_coverage

Context:

  • Pattern: staticFeatureFlags.IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS: true is compiled into the Reanimated Babel plugin output — it affects every useAnimatedStyle / withTiming / withSpring call in the entire app on iOS, not just the amount screen.
  • Risk: The PR's own checklist marks "iOS build with a fresh pod install" as unchecked. If the flag triggers an assertion in the Reanimated native module (e.g., accessing the shadow tree on the wrong thread), it silently breaks all animations app-wide on iOS.
  • Impact: Potential animation regressions across the whole iOS app (amount screen, scan screen, any other screens using Reanimated).
  • Trigger: First iOS release build after pod install with the new flag.

Recommendation: Confirm the iOS native build passes with IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS: true before merging (the PR's own checkbox covers this). If the flag is not yet validated on iOS, consider shipping only ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS: true initially and following up with the iOS flag after the pod install test passes.

Drop IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS: the per-frame shadow-tree commit
cost was only measured on low-end Android hardware, and the flag changes
every Reanimated animation on iOS. Also remove the keypad's handlePress
pass-through wrapper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – pos-demo — c848c11f Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant